namespace taurus.client{

    const enum ByteArraySize {

        SIZE_OF_BOOLEAN = 1,

        SIZE_OF_INT8 = 1,

        SIZE_OF_INT16 = 2,

        SIZE_OF_INT32 = 4,

        SIZE_OF_UINT8 = 1,

        SIZE_OF_UINT16 = 2,

        SIZE_OF_UINT32 = 4,

        SIZE_OF_FLOAT32 = 4,

        SIZE_OF_FLOAT64 = 8
    }
    
    function int64BEtoNumber(bytes:Uint8Array, pos:number) {
        let sign = bytes[pos+0] >> 7;
        let sum = 0;
        let digits = 1;
        for (let i = 0; i < 8; i++) {
          let value = bytes[pos+(7 - i)];
          sum += (sign ? value ^ 0xFF : value) * digits;
          digits *= 0x100;
        }
        return sign ? -1 - sum : sum;
      }
      
      function numberToInt64BE(val:number) {
        let result = [];
        let sign = val < 0;
        if (sign) val = -1 - val;
        for (let i = 0; i < 8; i++) {
          let mod = val % 0x100;
          val = (val - mod) / 0x100;
          result[7 - i] = sign ? mod ^ 0xFF : mod;
        }         
        return result;
      }

    /**
     * ByteArray 类提供用于优化读取、写入以及处理二进制数据的方法和属性。
     */
    export class ByteArray {
        public littleEndian:boolean = false;

        protected bufferExtSize = 0;//Buffer expansion size

        protected data: DataView;

        protected _bytes: Uint8Array;
        protected _position: number;

        /**
         * 
         * 已经使用的字节偏移量
         */
        protected write_position: number;

        


        constructor(buffer?: ArrayBuffer | Uint8Array, bufferExtSize = 0) {
            if (bufferExtSize < 0) {
                bufferExtSize = 0;
            }
            this.bufferExtSize = bufferExtSize;
            let bytes: Uint8Array, wpos = 0;
            if (buffer) {//有数据，则可写字节数从字节尾开始
                let uint8: Uint8Array;
                if (buffer instanceof Uint8Array) {
                    uint8 = buffer;
                    wpos = buffer.length;
                } else {
                    wpos = buffer.byteLength;
                    uint8 = new Uint8Array(buffer);
                }
                if (bufferExtSize == 0) {
                    bytes = new Uint8Array(wpos);
                }
                else {
                    let multi = (wpos / bufferExtSize | 0) + 1;
                    bytes = new Uint8Array(multi * bufferExtSize);
                }
                bytes.set(uint8);
            } else {
                bytes = new Uint8Array(bufferExtSize);
            }
            this.write_position = wpos;
            this._position = 0;
            this._bytes = bytes;
            this.data = new DataView(bytes.buffer);
        }

        /**
         * 可读的剩余字节数
         */
        public get readAvailable() {
            return this.write_position - this._position;
        }

        public get buffer(): ArrayBuffer {
            return this.data.buffer.slice(0, this.write_position);
        }

        public get rawBuffer(): ArrayBuffer {
            return this.data.buffer;
        }


        public set buffer(value: ArrayBuffer) {
            let wpos = value.byteLength;
            let uint8 = new Uint8Array(value);
            let bufferExtSize = this.bufferExtSize;
            let bytes: Uint8Array;
            if (bufferExtSize == 0) {
                bytes = new Uint8Array(wpos);
            }
            else {
                let multi = (wpos / bufferExtSize | 0) + 1;
                bytes = new Uint8Array(multi * bufferExtSize);
            }
            bytes.set(uint8);
            this.write_position = wpos;
            this._bytes = bytes;
            this.data = new DataView(bytes.buffer);
        }

        public get bytes(): Uint8Array {
            return this._bytes;
        }

        public get dataView(): DataView {
            return this.data;
        }

        public set dataView(value: DataView) {
            this.buffer = value.buffer;
        }

        /**
         * 将文件指针的当前位置
         */
        public get position(): number {
            return this._position;
        }

        public set position(value: number) {
            this._position = value;
            if (value > this.write_position) {
                this.write_position = value;
            }
        }

        /**
         * ByteArray 对象的长度（以字节为单位）。
         */
        public get length(): number {
            return this.write_position;
        }

        public set length(value: number) {
            this.write_position = value;
            if (this.data.byteLength > value) {
                this._position = value;
            }
            this._validateBuffer(value);
        }

        protected _validateBuffer(value: number) {
            if (this.data.byteLength < value) {
                let be = this.bufferExtSize;
                let tmp: Uint8Array;
                if (be == 0) {
                    tmp = new Uint8Array(value);
                }
                else {
                    let nLen = ((value / be >> 0) + 1) * be;
                    tmp = new Uint8Array(nLen);
                }
                tmp.set(this._bytes);
                this._bytes = tmp;
                this.data = new DataView(tmp.buffer);
            }
        }

        /**
         * 可从字节数组的当前位置到数组末尾读取的数据的字节数。
         */
        public get bytesAvailable(): number {
            return this.data.byteLength - this._position;
        }

        /**
         * 清除字节数组的内容，并将 length 和 position 属性重置为 0。
         */
        public clear(): void {
            let buffer = new ArrayBuffer(this.bufferExtSize);
            this.data = new DataView(buffer);
            this._bytes = new Uint8Array(buffer);
            this._position = 0;
            this.write_position = 0;
        }


        /**
         * 从字节流中读取布尔值。读取单个字节，如果字节非零，则返回 true，否则返回 false
         */
        public readBoolean(): boolean {
            if (this.validate(ByteArraySize.SIZE_OF_BOOLEAN)) return !!this._bytes[this.position++];
        }


        /**
         * 从字节流中读取带符号的字节
         */
        public readByte(): number {
            if (this.validate(ByteArraySize.SIZE_OF_INT8)) return this.data.getInt8(this.position++);
        }

        /**
         * 从字节流中读取 length 参数指定的数据字节数。
         * @param length 要读取的字节数。默认值 0 导致读取所有可用的数据
         */
        public readBytes(length: number = 0): ByteArray {
            let pos = this._position;
            let available = this.write_position - pos;
            if (available < 0) {
                log.error("1025");
                return;
            }
            if (length == 0) {
                length = available;
            }
            else if (length > available) {
                log.error("1025");
                return;
            }
            var bytes = new Uint8Array(length);
            bytes.set(this._bytes.subarray(pos, pos + length), 0);
            this.position += length;
            return new ByteArray(bytes);
        }

        /** uncompress需要引用zlib.min.js */
        public uncompress():void{
            var inflate = new Zlib.Inflate(this._bytes);
            var inbuffer: Int8Array = inflate.decompress();
            this._bytes = inbuffer;
            this.write_position = inbuffer.length;
            this._position = 0;
            this.data = new DataView(this._bytes.buffer);
        }
 
         /** compress需要引用zlib.min.js */
        public compress():void{
            var deflate = new Zlib.Deflate(this._bytes);
            var inbuffer: Int8Array = deflate.compress();
            this._bytes = inbuffer;
            this.write_position = inbuffer.length;
            this._position = 0;
            this.data = new DataView(this._bytes.buffer);
        }

        /**
         * 从字节流中读取一个 IEEE 754 双精度（64 位）浮点数
         * @return 双精度（64 位）浮点数
         */
        public readDouble(): number {
            if (this.validate(ByteArraySize.SIZE_OF_FLOAT64)) {
                let value = this.data.getFloat64(this._position, this.littleEndian);
                this.position += ByteArraySize.SIZE_OF_FLOAT64;
                return value;
            }
        }

        /**
         * 从字节流中读取一个 IEEE 754 单精度（32 位）浮点数
         */
        public readFloat(): number {
            if (this.validate(ByteArraySize.SIZE_OF_FLOAT32)) {
                let value = this.data.getFloat32(this._position, this.littleEndian);
                this.position += ByteArraySize.SIZE_OF_FLOAT32;
                return value;
            }
        }

        /**
         * 从字节流中读取一个带符号的 32 位整数
         */
        public readInt(): number {
            if (this.validate(ByteArraySize.SIZE_OF_INT32)) {
                let value = this.data.getInt32(this._position, this.littleEndian);
                this.position += ByteArraySize.SIZE_OF_INT32;
                return value;
            }
        }

        /**
         * 从字节流中读取一个带符号的 64 位整数
         */
        public readLong(): number {
            if (this.validate(ByteArraySize.SIZE_OF_INT32)) {
                let value = int64BEtoNumber(this.bytes,this._position);
                this.position += ByteArraySize.SIZE_OF_FLOAT64;
                return value;
            }
        }

        /**
         * 从字节流中读取一个带符号的 16 位整数
         */
        public readShort(): number {
            if (this.validate(ByteArraySize.SIZE_OF_INT16)) {
                let value = this.data.getInt16(this._position, this.littleEndian);
                this.position += ByteArraySize.SIZE_OF_INT16;
                return value;
            }
        }

        /**
         * 从字节流中读取无符号的字节
         */
        public readUByte(): number {
            if (this.validate(ByteArraySize.SIZE_OF_UINT8)) return this._bytes[this.position++];
        }

        /**
         * 从字节流中读取一个无符号的 32 位整数
         */
        public readUInt(): number {
            if (this.validate(ByteArraySize.SIZE_OF_UINT32)) {
                let value = this.data.getUint32(this._position, this.littleEndian);
                this.position += ByteArraySize.SIZE_OF_UINT32;
                return value;
            }
        }

        /**
         * 从字节流中读取一个无符号的 16 位整数
         */
        public readUShort(): number {
            if (this.validate(ByteArraySize.SIZE_OF_UINT16)) {
                let value = this.data.getUint16(this._position, this.littleEndian);
                this.position += ByteArraySize.SIZE_OF_UINT16;
                return value;
            }
        }

        /**
         * 从字节流中读取一个 UTF-8 字符串。假定字符串的前缀是无符号的短整型（以字节表示长度）
         */
        public readString(): string {
            let length = this.readUShort();
            if (length > 0) {
                return this._readUTFBytes(length);
            } else {
                return "";
            }
        }

        /**
         * 从字节流中读取一个由 length 参数指定的 UTF-8 字节序列，并返回一个字符串
         */
        private _readUTFBytes(length: number): string {
            if (!this.validate(length)) {
                return;
            }
            let data = this.data;
            let bytes = new Uint8Array(data.buffer, data.byteOffset + this._position, length);
            this.position += length;
            return this.decodeUTF8(bytes);
        }


        /**
         * 写入布尔值。
         */
        public writeBoolean(value: boolean): void {
            this.validateBuffer(ByteArraySize.SIZE_OF_BOOLEAN);
            this._bytes[this.position++] = +value;
        }

 
        /**
         * 在字节流中写入一个字节
         */
        public writeByte(value: number): void {
            this.validateBuffer(ByteArraySize.SIZE_OF_INT8);
            this._bytes[this.position++] = value & 0xff;
        }

        /**
         * 将指定字节数组 bytes（起始偏移量为 offset，从零开始的索引）中包含 length 个字节的字节序列写入字节流
         * 如果省略 length 参数，则使用默认长度 0；该方法将从 offset 开始写入整个缓冲区。如果还省略了 offset 参数，则写入整个缓冲区
         * 如果 offset 或 length 超出范围，它们将被锁定到 bytes 数组的开头和结尾
         * @param bytes ByteArray 对象
         * @param offset 从 0 开始的索引，表示在数组中开始写入的位置
         * @param length 一个无符号整数，表示在缓冲区中的写入范围
         */
        public writeBytes(bytes: Uint8Array, offset: number = 0, length: number = 0): void {
            let writeLength: number;
            if (offset < 0) {
                return;
            }
            if (length < 0) {
                return;
            } else if (length == 0) {
                writeLength = bytes.length - offset;
            } else {
                writeLength = Math.min(bytes.length - offset, length);
            }
            if (writeLength > 0) {
                this.validateBuffer(writeLength);
                this._bytes.set(bytes.subarray(offset, offset + writeLength), this._position);
                this.position = this._position + writeLength;
            }
        }
        
        /**
         * 在字节流中写入一个 Long
         */
        public writeLong(value: number): void {
            this.validateBuffer(ByteArraySize.SIZE_OF_FLOAT64);
            this._bytes.set(numberToInt64BE(value),this._position);
            this.position += ByteArraySize.SIZE_OF_FLOAT64;
        }
        
        /**
         * 在字节流中写入一个 IEEE 754 双精度（64 位）浮点数
         * @param value 双精度（64 位）浮点数
         */
        public writeDouble(value: number): void {
            this.validateBuffer(ByteArraySize.SIZE_OF_FLOAT64);
            this.data.setFloat64(this._position, value, this.littleEndian);
            this.position += ByteArraySize.SIZE_OF_FLOAT64;
        }

        /**
         * 在字节流中写入一个 IEEE 754 单精度（32 位）浮点数
         * @param value 单精度（32 位）浮点数
         */
        public writeFloat(value: number): void {
            this.validateBuffer(ByteArraySize.SIZE_OF_FLOAT32);
            this.data.setFloat32(this._position, value, this.littleEndian);
            this.position += ByteArraySize.SIZE_OF_FLOAT32;
        }

        /**
         * 在字节流中写入一个带符号的 32 位整数
         */
        public writeInt(value: number): void {
            this.validateBuffer(ByteArraySize.SIZE_OF_INT32);
            this.data.setInt32(this._position, value, this.littleEndian);
            this.position += ByteArraySize.SIZE_OF_INT32;
        }

        /**
         * 在字节流中写入一个 16 位整数。使用参数的低 16 位。忽略高 16 位
         */
        public writeShort(value: number): void {
            this.validateBuffer(ByteArraySize.SIZE_OF_INT16);
            this.data.setInt16(this._position, value, this.littleEndian);
            this.position += ByteArraySize.SIZE_OF_INT16;
        }

        /**
         * 在字节流中写入一个无符号的 32 位整数
         */
        public writeUInt(value: number): void {
            this.validateBuffer(ByteArraySize.SIZE_OF_UINT32);
            this.data.setUint32(this._position, value, this.littleEndian);
            this.position += ByteArraySize.SIZE_OF_UINT32;
        }

        /**
         * 在字节流中写入一个无符号的 16 位整数
         */
        public writeUShort(value: number): void {
            this.validateBuffer(ByteArraySize.SIZE_OF_UINT16);
            this.data.setUint16(this._position, value, this.littleEndian);
            this.position += ByteArraySize.SIZE_OF_UINT16;
        }

        /**
         * 将 UTF-8 字符串写入字节流。
         */
        public writeString(value: string): void {
            let utf8bytes: ArrayLike<number> = this.encodeUTF8(value);
            let length: number = utf8bytes.length;
            this.validateBuffer(ByteArraySize.SIZE_OF_UINT32 + length);
            this.data.setUint16(this._position, length, this.littleEndian);
            this.position += ByteArraySize.SIZE_OF_UINT32;
            this._writeUint8Array(utf8bytes, false);
        }

        public toString(): string {
            return "[ByteArray] length:" + this.length + ", bytesAvailable:" + this.bytesAvailable;
        }

        /**
         * 将 Uint8Array 写入字节流
         */
        private _writeUint8Array(bytes: Uint8Array | ArrayLike<number>, validateBuffer: boolean = true): void {
            let pos = this._position;
            let npos = pos + bytes.length;
            if (validateBuffer) {
                this.validateBuffer(npos);
            }
            this.bytes.set(bytes, pos);
            this.position = npos;
        }

        public validate(len: number): boolean {
            let bl = this._bytes.length;
            if (bl > 0 && this._position + len <= bl) {
                return true;
            }
        }

        /**********************/
        /*  PRIVATE METHODS   */
        /**********************/
        /**
         * @private
         * @param len
         * @param needReplace
         */
        protected validateBuffer(len: number): void {
            this.write_position = len > this.write_position ? len : this.write_position;
            len += this._position;
            this._validateBuffer(len);
        }

        /**
         * @private
         * UTF-8 Encoding/Decoding
         */
        private encodeUTF8(str: string): Uint8Array {
            let pos: number = 0;
            let codePoints = this.stringToCodePoints(str);
            let outputBytes = [];

            while (codePoints.length > pos) {
                let code_point: number = codePoints[pos++];

                if (this.inRange(code_point, 0xD800, 0xDFFF)) {
                    log.error("range error!{0}",code_point);
                }
                else if (this.inRange(code_point, 0x0000, 0x007f)) {
                    outputBytes.push(code_point);
                } else {
                    let count, offset;
                    if (this.inRange(code_point, 0x0080, 0x07FF)) {
                        count = 1;
                        offset = 0xC0;
                    } else if (this.inRange(code_point, 0x0800, 0xFFFF)) {
                        count = 2;
                        offset = 0xE0;
                    } else if (this.inRange(code_point, 0x10000, 0x10FFFF)) {
                        count = 3;
                        offset = 0xF0;
                    }

                    outputBytes.push(this.div(code_point, Math.pow(64, count)) + offset);

                    while (count > 0) {
                        let temp = this.div(code_point, Math.pow(64, count - 1));
                        outputBytes.push(0x80 + (temp % 64));
                        count -= 1;
                    }
                }
            }
            return new Uint8Array(outputBytes);
        }

        private decodeUTF8(data: Uint8Array): string {
            let fatal: boolean = false;
            let pos: number = 0;
            let result: string = "";
            let code_point: number;
            let utf8_code_point = 0;
            let utf8_bytes_needed = 0;
            let utf8_bytes_seen = 0;
            let utf8_lower_boundary = 0;

            while (data.length > pos) {

                let _byte = data[pos++];

                if (_byte == this.EOF_byte) {
                    if (utf8_bytes_needed != 0) {
                        code_point = this.decoderError(fatal);
                    } else {
                        code_point = this.EOF_code_point;
                    }
                } else {

                    if (utf8_bytes_needed == 0) {
                        if (this.inRange(_byte, 0x00, 0x7F)) {
                            code_point = _byte;
                        } else {
                            if (this.inRange(_byte, 0xC2, 0xDF)) {
                                utf8_bytes_needed = 1;
                                utf8_lower_boundary = 0x80;
                                utf8_code_point = _byte - 0xC0;
                            } else if (this.inRange(_byte, 0xE0, 0xEF)) {
                                utf8_bytes_needed = 2;
                                utf8_lower_boundary = 0x800;
                                utf8_code_point = _byte - 0xE0;
                            } else if (this.inRange(_byte, 0xF0, 0xF4)) {
                                utf8_bytes_needed = 3;
                                utf8_lower_boundary = 0x10000;
                                utf8_code_point = _byte - 0xF0;
                            } else {
                                this.decoderError(fatal);
                            }
                            utf8_code_point = utf8_code_point * Math.pow(64, utf8_bytes_needed);
                            code_point = null;
                        }
                    } else if (!this.inRange(_byte, 0x80, 0xBF)) {
                        utf8_code_point = 0;
                        utf8_bytes_needed = 0;
                        utf8_bytes_seen = 0;
                        utf8_lower_boundary = 0;
                        pos--;
                        code_point = this.decoderError(fatal, _byte);
                    } else {

                        utf8_bytes_seen += 1;
                        utf8_code_point = utf8_code_point + (_byte - 0x80) * Math.pow(64, utf8_bytes_needed - utf8_bytes_seen);

                        if (utf8_bytes_seen !== utf8_bytes_needed) {
                            code_point = null;
                        } else {

                            let cp = utf8_code_point;
                            let lower_boundary = utf8_lower_boundary;
                            utf8_code_point = 0;
                            utf8_bytes_needed = 0;
                            utf8_bytes_seen = 0;
                            utf8_lower_boundary = 0;
                            if (this.inRange(cp, lower_boundary, 0x10FFFF) && !this.inRange(cp, 0xD800, 0xDFFF)) {
                                code_point = cp;
                            } else {
                                code_point = this.decoderError(fatal, _byte);
                            }
                        }

                    }
                }
                //Decode string
                if (code_point !== null && code_point !== this.EOF_code_point) {
                    if (code_point <= 0xFFFF) {
                        if (code_point > 0) result += String.fromCharCode(code_point);
                    } else {
                        code_point -= 0x10000;
                        result += String.fromCharCode(0xD800 + ((code_point >> 10) & 0x3ff));
                        result += String.fromCharCode(0xDC00 + (code_point & 0x3ff));
                    }
                }
            }
            return result;
        }

        /**
         * @private
         *
         * @param fatal
         * @param opt_code_point
         * @returns
         */
        private decoderError(fatal, opt_code_point?): number {
            return opt_code_point || 0xFFFD;
        }

        /**
         * @private
         */
        private EOF_byte: number = -1;
        /**
         * @private
         */
        private EOF_code_point: number = -1;

        /**
         * @private
         *
         * @param a
         * @param min
         * @param max
         */
        private inRange(a, min, max) {
            return min <= a && a <= max;
        }

        /**
         * @private
         *
         * @param n
         * @param d
         */
        private div(n, d) {
            return Math.floor(n / d);
        }

        /**
         * @private
         *
         * @param string
         */
        private stringToCodePoints(string) {
            /** @type {Array.<number>} */
            let cps = [];
            // Based on http://www.w3.org/TR/WebIDL/#idl-DOMString
            let i = 0, n = string.length;
            while (i < string.length) {
                let c = string.charCodeAt(i);
                if (!this.inRange(c, 0xD800, 0xDFFF)) {
                    cps.push(c);
                } else if (this.inRange(c, 0xDC00, 0xDFFF)) {
                    cps.push(0xFFFD);
                } else { // (inRange(c, 0xD800, 0xDBFF))
                    if (i == n - 1) {
                        cps.push(0xFFFD);
                    } else {
                        let d = string.charCodeAt(i + 1);
                        if (this.inRange(d, 0xDC00, 0xDFFF)) {
                            let a = c & 0x3FF;
                            let b = d & 0x3FF;
                            i += 1;
                            cps.push(0x10000 + (a << 10) + b);
                        } else {
                            cps.push(0xFFFD);
                        }
                    }
                }
                i += 1;
            }
            return cps;
        }
    }
}